package entidades;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import enums.EventoAtributos;
import enums.FinalidadeTipoSala;
import enums.SalaAtributos;
import util.StringsUtil;

/**
 * Classe que gerencia eventos e salas
 * 
 * @author Raildo, Gabriel, Tales e Arthur
 * @version 1.0 Classe que gerencia os objetos da classe Sala e da classe
 *          Eventos.
 * 
 */
public class Gerenciador implements Serializable {

	private static final long serialVersionUID = -906572051099606037L;
	private List<Alocavel> listaDeSalas;
	private List<Escalonavel> listaDeEventos;
	private int indiceIteratorEsc, indiceIteratorAloc;

	private final String STRING_VAZIA = "",
			MSG_EXCEP_JA_EXISTE_SALA_ID = "Ja existe sala com esta identificacao.",
			MSG_EXCEO_JA_EXISTE_EVENTO_ID = "Ja existe evento com esta identificacao.",
			MSG_EXCEP_SALA_EVENTO_NAO_EXISTEM = "Sala/Evento nao existe.",
			MSG_EXCEP_EVENTO_JA_ALOCADO = "O Evento ja foi alocado anteriormente.",
			MSG_EXCEP_ESCRITORIO_NAO_ALOCAVEL = "Escritorios nao sao escalonaveis.",
			MSG_EXCEP_LABS_ABERTOS_NAO_ESCALONAVEIS = "Laboratorios abertos nao sao escalonaveis.",
			MSG_EXCEP_CONF_NORM_N_ESCALON = "Salas de Conferencia do tipo Normal nao sao escalonaveis para eventos repetitivos.",
			MSG_EXCEP_SALA_N_EXISTE = "Sala nao existe.", TIPO = "tipo", ATRIBUTO_INVALIDO = "Atributo invalido",
			NOME = "nome", REPETICOES = "repeticoes", CONTATO = "contato",
			HORARIO = "horario", INICIO = "inicio", FIM = "fim", AREA = "area",
			SALA_NAO_EXISTE = "Sala nao existe.", 
			SALA = "sala", EVENTO = "evento", 
			NENHUM_EVENTO_ENCONTRADO = "Nenhum evento encontrado.",
			MSG_EXCEP_EVENTO_NAO_EXISTE = "Evento nao existe.",
			MSG_EXCEP_EVENTO_N_ALOCADO = "O evento nao esta alocado.",
			MSG_EXCEP_ENTRADA_INVALIDA = "Entrada Invalida",
			MSG_EXCEP_ATRIBUTO_N_EXISTE = "Atributo nao existe.";

	/**
	 * Contrutor do gerenciador
	 * 
	 */
	public Gerenciador() {
		setListaDeSalas(new ArrayList<Alocavel>());
		setListaDeEventos(new ArrayList<Escalonavel>());
	}

	public List<Escalonavel> getListaDeEventos() {
		return this.listaDeEventos;
	}

	public List<Alocavel> getListaDeSalas() {
		return this.listaDeSalas;
	}

	/**
	 * Metodo para criar uma sala a partir dos atributos passados e adiciona-la
	 * a List<Salas>.
	 * 
	 * @param id
	 *            id da sala
	 * @param capacidade
	 *            capacidade da sala
	 * @param finalidade
	 *            finalidade da sala
	 * @param tipo
	 *            tipo da sala
	 * @throws Exception
	 *             Se ja exista uma sala com o id que foi passado
	 */
	public void adicionarSala(String id, int capacidade, String finalidade,
			String tipo) throws Exception {

		if (!verificaDisponibilidadeDeSalaPeloId(id)) {
			throw new Exception(MSG_EXCEP_JA_EXISTE_SALA_ID);
		}

		guardaSala(new Sala(id, capacidade, finalidade, tipo));
	}

	/**
	 * Metodo para criar uma sala a partir dos atributos passados e adiciona-la
	 * a List<Salas>.
	 * 
	 * @param id
	 *            id da sala
	 * @param capacidade
	 *            capacidade da sala
	 * @param finalidade
	 *            finalidade da sala
	 * @param tipo
	 *            tipo da sala
	 * @param apelido
	 *            apelido da sala
	 * @throws Exception
	 *             Se ja exista uma sala com o id que foi passado
	 */
	public void adicionarSala(String id, int capacidade, String finalidade,
			String tipo, String apelido) throws Exception {

		if (!verificaDisponibilidadeDeSalaPeloId(id)) {
			throw new Exception(MSG_EXCEP_JA_EXISTE_SALA_ID);
		}

		guardaSala(new Sala(id, capacidade, finalidade, tipo, apelido));
	}

	/**
	 * Metodo para criar uma sala a partir dos atributos passados e adiciona-la
	 * a List<Salas>.
	 * 
	 * @param id
	 *            id da sala
	 * @param capacidade
	 *            capacidade da sala
	 * @param finalidade
	 *            finalidade da sala
	 * @param tipo
	 *            tipo da sala
	 * @param apelido
	 *            apelido da sala
	 * @param aberto
	 *            estado da sala
	 * @throws Exception
	 *             Se ja exista uma sala com o id que foi passado
	 */
	public void adicionarSala(String id, int capacidade, String finalidade,
			String tipo, String apelido, boolean aberto) throws Exception {
		if (!verificaDisponibilidadeDeSalaPeloId(id)) {
			throw new Exception(MSG_EXCEP_JA_EXISTE_SALA_ID);
		}
		guardaSala(new Sala(id, capacidade, finalidade, tipo, apelido, aberto));
	}

	/**
	 * Método para alocar um Evento já criado a uma Sala.
	 * 
	 * @param idEvento
	 *            - identificação do evento a ser alocado.
	 * @param idSala
	 *            - identificação da sala onde o evento será alocado.
	 * @throws Exception
	 *             - Lança exceção se a Sala ou o Evento não existirem, ou
	 *             se o Evento já estiver alocado, ou se a Sala informada for
	 *             um Escritório, ou se a Sala informada for um Laboratório
	 *             aberto ou se um evento com repetições for passado para uma
	 *             Sala de Conferência Normal.
	 */
	public void alocarEvento(String idEvento, String idSala) throws Exception {

		Alocavel sala = localizaSalaPorId(idSala);
		Escalonavel evento = localizaEventoPorId(idEvento);

		if (sala == null || evento == null) {
			throw new Exception(MSG_EXCEP_SALA_EVENTO_NAO_EXISTEM);
		}

		if (evento.getAtributo(EventoAtributos.ESTAALOCADO.toString()).equals(true)) {
			throw new Exception(MSG_EXCEP_EVENTO_JA_ALOCADO);
		}

		if (( (String) sala.getAtributo(SalaAtributos.FINALIDADE.toString()) ).equalsIgnoreCase(
				FinalidadeTipoSala.ESCRITORIO.getFinalidade())) {
			throw new Exception(MSG_EXCEP_ESCRITORIO_NAO_ALOCAVEL);
		}

		if (( (String) sala.getAtributo(SalaAtributos.FINALIDADE.toString()) ).equalsIgnoreCase(
				FinalidadeTipoSala.LABORATORIO_COMPUTACAO.getFinalidade())
				&& (sala.getAtributo(SalaAtributos.ABERTO.toString()).equals(true))) {
			throw new Exception(MSG_EXCEP_LABS_ABERTOS_NAO_ESCALONAVEIS);
		}

		if (ehSalaConferenciaNormal(sala) && ((Integer)evento.getAtributo(EventoAtributos.REPETICOES.toString()) > 0)) {
			throw new Exception(MSG_EXCEP_CONF_NORM_N_ESCALON);
		}

		sala.alocarEvento(evento);
		evento.setAtributo(EventoAtributos.ESTAALOCADO.toString(), true);
	}

	private boolean ehSalaConferenciaNormal(Alocavel sala2) throws Exception {
		return ( (String) sala2.getAtributo(SalaAtributos.FINALIDADE.toString()) ).equalsIgnoreCase(
				FinalidadeTipoSala.SALA_DE_CONFERENCIA_NORMAL.getFinalidade())
				&& ( (String) sala2.getAtributo(SalaAtributos.TIPO.toString()) )
						.equalsIgnoreCase(
								FinalidadeTipoSala.SALA_DE_CONFERENCIA_NORMAL
										.getTipo());

	}

	/**
	 * Método para recuperar um atributo da classe Sala
	 * 
	 * @param id
	 *            - identificação da sala a ser procurada.
	 * @param atributo
	 *            - atributo da sala a ser buscada
	 * @return - retorna o valor do atributo a ser pesquisado
	 * @throws Exception
	 *             - Lança Exceção se o objeto da classe Sala for null, ou se
	 *             for pedido o atributo Tipo do Escritorio.
	 */
	public String getAtributoSala(String id, String atributo) throws Exception {
		Alocavel sala = localizaSalaPorId(id);

		if (sala == null) {
			throw new Exception(MSG_EXCEP_SALA_N_EXISTE);
		}

		if (( (String) sala.getAtributo(SalaAtributos.FINALIDADE.toString()) ).equalsIgnoreCase(
				FinalidadeTipoSala.ESCRITORIO.getFinalidade())
				&& atributo.equalsIgnoreCase(TIPO)) {
			throw new Exception(ATRIBUTO_INVALIDO);
		}
		
		return String.valueOf(sala.getAtributo(atributo));

//		if (atributo.equalsIgnoreCase(FINALIDADE)) {
//			return (String) sala.getAtributo(SalaAtributos.FINALIDADE.toString());
//		}
//
//		if (atributo.equalsIgnoreCase(CAPACIDADE)) {
//			return String.valueOf(sala.getAtributo(SalaAtributos.CAPACIDADE.toString()));
//		}
//
//		if (atributo.equalsIgnoreCase(APELIDO)) {
//			return (String) sala.getAtributo(SalaAtributos.APELIDO.toString());
//		}
//
//		if (atributo.equalsIgnoreCase(TIPO)) {
//			return (String) sala.getAtributo(SalaAtributos.TIPO.toString());
//		}

//		return ATRIBUTO_INVALIDO;
	}

	/**
	 * Metodo para remover uma Sala do sistema. Todos os eventos da sala serão
	 * setados como não-alocados
	 * 
	 * @param id
	 *            - id da sala a ser ecluida
	 * @throws Exception
	 *             - Uma Exceção será lançada se a identificação da sala
	 *             for null.
	 */
	public void removerSala(String id) throws Exception {
		Alocavel sala = localizaSalaPorId(id);

		if (sala == null) {
			throw new Exception(SALA_NAO_EXISTE);
		}
		for (int i = 0; i < sala.getAlocadoPara().size(); i++) {
			sala.getAlocadoPara().get(i).setAtributo(EventoAtributos.ESTAALOCADO.toString(), false);
		}

		getListaDeSalas().remove(sala);

	}
	
	public void removerSala(Alocavel aloc) throws Exception {
		removerSala( (String) aloc.getAtributo(EventoAtributos.ID.toString()));
				
	}

	public void setListaDeSalas(List<Alocavel> listaDeSalas) {
		this.listaDeSalas = listaDeSalas;
	}

	public void setListaDeEventos(List<Escalonavel> listaDeEventos) {
		this.listaDeEventos = listaDeEventos;
	}

	/**
	 * Método para criar um Evento baseado nos atributos fornecidos.
	 * 
	 * @param id
	 *            - identificação do Evento
	 * @param nome
	 *            - nome do Evento
	 * @param inicio
	 *            - data e hora de inicio
	 * @param fim
	 *            - data e hora do fim do evento
	 * @param area
	 *            - area de afinidade do evento
	 * @param contato
	 *            - nome do responsavel pelo Evento
	 * @throws Exception
	 *             - Uma Exceção será lançada caso a identificação
	 *             fornecida já pertencer a outro Evento já criado.
	 */
	public void adicionarEvento(String id, String nome, String inicio,
			String fim, String area, String contato) throws Exception {

		if (!verificaDisponibilidadeDeEventoPeloId(id)) {
			throw new Exception(MSG_EXCEO_JA_EXISTE_EVENTO_ID);
		}

		guardaEvento(new Evento(id, nome, inicio, fim, area, contato));
	}

	/**
	 * Método para criar um Evento baseado nos atributos fornecidos.
	 * 
	 * @param id
	 *            - identificação do Evento
	 * @param nome
	 *            - nome do Evento
	 * @param inicio
	 *            - data e hora de inicio
	 * @param fim
	 *            - data e hora do fim do evento
	 * @param area
	 *            - area de afinidade do evento
	 * @param contato
	 *            - nome do responsavel pelo Evento
	 * @param repeticoes
	 *            - numero de semanas em que o evento irá repetir
	 * @throws Exception
	 *             - Uma Exceção será lançada caso a identificação
	 *             fornecida já pertencer a outro Evento já criado.
	 */
	public void adicionarEvento(String id, String nome, String inicio,
			String fim, String area, String contato, int repeticoes)
			throws Exception {

		if (!verificaDisponibilidadeDeEventoPeloId(id)) {
			throw new Exception(MSG_EXCEO_JA_EXISTE_EVENTO_ID);
		}
		guardaEvento(new Evento(id, nome, inicio, fim, area, contato,
				repeticoes));
	}

	/**
	 * Metodo para cancelar um Evento previamente criado
	 * 
	 * @param id
	 *            - identificação do evento criado.
	 * @throws Exception
	 *             - Uma Exceção será lançada se a identificação não
	 *             pertencer a nenhum Evento.
	 */
	public void cancelarEvento(String id) throws Exception {

		Escalonavel eventoASerCancelado = localizaEventoPorId(id);

		if (eventoASerCancelado == null) {
			throw new Exception(MSG_EXCEP_EVENTO_NAO_EXISTE);
		}

		if (eventoASerCancelado.getAtributo(EventoAtributos.ESTAALOCADO.toString()).equals(true)) {
			for (int i = 0; i < getListaDeSalas().size(); i++) {
				Alocavel sala = getListaDeSalas().get(i);
				
				Iterator<Escalonavel> it = sala.iterator();
				
				while (it.hasNext()){
					Escalonavel evento = it.next();
					if (eventoASerCancelado.equals(evento)) {
						it.remove();
						getListaDeEventos().remove(evento);
					}
				}
				
			}
		} else {
			for (int i = 0; i < getListaDeEventos().size(); i++) {
				if (getListaDeEventos().get(i).equals(eventoASerCancelado)) {
					getListaDeEventos().remove(eventoASerCancelado);
				}
			}
		}

	}

	/**
	 * 
	 * @param id
	 *            - identificação do Evento a ser desalocado. Entende-se por
	 *            desalocar, o processo de desvincular um evento a sua Sala.
	 * @throws Exception
	 *             - Uma Esceção será lançada se o Evento já estiver
	 *             alocado.
	 */
	public void desalocarEvento(String id) throws Exception {

		if (!eventoAlocado(id)) {
			throw new Exception(MSG_EXCEP_EVENTO_N_ALOCADO);
		}

		for (int i = 0; i < getListaDeSalas().size(); i++) {
			Alocavel sala = getListaDeSalas().get(i);
			
			Iterator<Escalonavel> it = sala.iterator();
			
			while (it.hasNext()){
				Escalonavel evento = it.next();
				if (((String) evento.getAtributo(EventoAtributos.ID.toString())).equalsIgnoreCase(id)) {
					it.remove();
				}
			}			
		}
	}

	private void desalocarEvento(Escalonavel escalonavel) throws Exception {
		
		desalocarEvento( (String) escalonavel.getAtributo(EventoAtributos.ID.toString()));
		
	}
	
	private boolean eventoAlocado(String id) throws Exception {

		for (int i = 0; i < getListaDeEventos().size(); i++) {
			Escalonavel ev = getListaDeEventos().get(i);
			if (( (String) ev.getAtributo(EventoAtributos.ID.toString()) ).equalsIgnoreCase(id) && ev.getAtributo(EventoAtributos.ESTAALOCADO.toString()).equals(true)) {
				return true;
			}
		}
		return false;

	}

	/**
	 * Metodo para recuperar uma String indicando um par Sala-Evento que
	 * contenha o atributo especificado e o valor fornecido.
	 * 
	 * @param atributo
	 * @param valor
	 * @return
	 * @throws Exception
	 */
	public String localizarEvento(String atributo, String valor)
			throws Exception {

		if (valor == null || valor.equals(STRING_VAZIA) || atributo == null
				|| atributo.equals(STRING_VAZIA)) {
			throw new Exception(MSG_EXCEP_ENTRADA_INVALIDA);
		}

		List<String> eventosLocalizados = new ArrayList<String>();

		for (int i = 0; i < getListaDeSalas().size(); i++) {
			Alocavel sala = getListaDeSalas().get(i);
			Iterator<Escalonavel> it = sala.iterator();
			
			while(it.hasNext()) {
				
				Escalonavel evento = it.next();
				if (atributo.equalsIgnoreCase(NOME)) {
					if (encontrou(evento, valor, NOME, SALA)) {

						eventosLocalizados.add(getListaDeSalas().get(i).getAtributo(SalaAtributos.ID.toString())
								+ ":"
								+ ((String)evento.getAtributo(EventoAtributos.ID.toString())));

					}
				}

				else if (atributo.equalsIgnoreCase(CONTATO)) {
					if (encontrou(evento, valor, CONTATO, SALA)) {

						eventosLocalizados.add(getListaDeSalas().get(i).getAtributo(SalaAtributos.ID.toString())
								+ ":"
								+ ((String) evento.getAtributo(EventoAtributos.ID.toString())));

					}
				}

				else if (atributo.equalsIgnoreCase(HORARIO)) {
					if (encontrou(evento, valor, HORARIO, EVENTO)) {

						eventosLocalizados.add(getListaDeSalas().get(i).getAtributo(SalaAtributos.ID.toString())
								+ ":"
								+ ((String) evento.getAtributo(EventoAtributos.ID.toString())));
					}
				}
			}
		}

		if (eventosLocalizados.size() == 0) {
			return NENHUM_EVENTO_ENCONTRADO;
		} else {

			return StringsUtil.formataStringSalaEvento(eventosLocalizados);
		}

	}

	//talvez esse metodo precise ser dividido em dois dependendo da divisao de classes
	private boolean encontrou(Escalonavel escalon, String valor, String atributo,
			String objeto) throws Exception {
		if (objeto.equalsIgnoreCase(SALA)) {
			if (atributo.equalsIgnoreCase(NOME)) {
				String nome = (String) escalon.getAtributo(EventoAtributos.NOME.toString());
				
				return nome.equalsIgnoreCase(valor)
						|| StringsUtil.contemPalavra(valor, nome);
			}
			if (atributo.equalsIgnoreCase(CONTATO)) {
				String contato = (String) escalon.getAtributo(EventoAtributos.CONTATO.toString());
				return contato.equalsIgnoreCase(valor)
						|| StringsUtil.contemPalavra(valor, contato);
			}
		}
		if (objeto.equalsIgnoreCase(EVENTO)) {
			return ((Tempo) escalon.getAtributo(EventoAtributos.INTERVALOTEMPO.toString()))
			.conflitoHorario(valor);
		}
		return false;
	}

	/**
	 * Metodo para recuperar um atributo especificado por parametro de um
	 * Evento.
	 * 
	 * @param id
	 *            - identificação do Evento
	 * @param atributo
	 *            - atributo do evento a ser pesquisado
	 * @return - o valor do atributo do Evento
	 * @throws Exception
	 *             - Uma exceção será lançada se não for localizado um
	 *             Evento pelo atributo passado.
	 */
	public String getAtributoEvento(String id, String atributo)
			throws Exception {
		Escalonavel evento = localizaEventoPorId(id);

		if (evento == null) {
			throw new Exception(MSG_EXCEP_EVENTO_NAO_EXISTE);
		}

		if (atributo.equalsIgnoreCase(NOME)) {
			return (String)evento.getAtributo(EventoAtributos.NOME.toString());
		} else if (atributo.equalsIgnoreCase(INICIO)) {
			return (String)evento.getAtributo(EventoAtributos.INICIO.toString());
		} else if (atributo.equalsIgnoreCase(FIM)) {
			return (String)evento.getAtributo(EventoAtributos.FIM.toString());
		} else if (atributo.equalsIgnoreCase(AREA)) {
			return String.valueOf(evento.getAtributo(EventoAtributos.AREA.toString()));
		} else if (atributo.equalsIgnoreCase(REPETICOES)) {
			return String.valueOf((Integer)evento.getAtributo(EventoAtributos.REPETICOES.toString()));
		}
		else{
			throw new Exception(MSG_EXCEP_ATRIBUTO_N_EXISTE);
		}
	}

	/**
	 * localiza a sala atraves de seu id
	 * 
	 * @param id
	 *            identificacao da Sala
	 * @return a sala pesquisada, null caso ela nao exista
	 * @throws Exception 
	 */
	private Alocavel localizaSalaPorId(String id) throws Exception {

		if (getListaDeSalas().size() > 0) {
			for (int indice = 0; indice < getListaDeSalas().size(); indice++) {

				if (getListaDeSalas().get(indice).getAtributo(SalaAtributos.ID.toString()).equals(id)) {

					return getListaDeSalas().get(indice);
				}
			}
		}

		return null;
	}

	/**
	 * Metodo para localizar um Evento pela identificação fornecida.
	 * 
	 * @param id
	 *            - identificação do Evento
	 * @return - o Evento localizado
	 * @throws Exception 
	 */
	public Escalonavel localizaEventoPorId(String id) throws Exception {

		if (getListaDeEventos().size() > 0) {
			for (int indice = 0; indice < getListaDeEventos().size(); indice++) {

				if (((String) getListaDeEventos().get(indice).getAtributo(EventoAtributos.ID.toString())).equals(id)) {

					return getListaDeEventos().get(indice);
				}
			}
		}
		return null;
	}

	private void guardaSala(Sala sala) {
		getListaDeSalas().add(sala);
	}

	private void guardaEvento(Evento evento) {
		getListaDeEventos().add(evento);
	}

	/**
	 * Metodo que verifica a disponibilidade da sala atraves do id
	 * 
	 * @param id
	 *            identificacao da sala
	 * @return um boolean indicando se a sala foi localizada
	 * @throws Exception
	 */
	private boolean verificaDisponibilidadeDeSalaPeloId(String id)
			throws Exception {
		if (localizaSalaPorId(id) == null) {
			return true;
		}
		return false;
	}

	/**
	 * Metodo que verifica a disponibilidade do evento atraves do id
	 * 
	 * @param id
	 *            identificacao do evento
	 * @return um boolean indicando se o evento foi localizado
	 * @throws Exception
	 */
	private boolean verificaDisponibilidadeDeEventoPeloId(String id)
			throws Exception {
		if (localizaEventoPorId(id) == null) {
			return true;
		}
		return false;
	}

	/**
	 * Metodo para encerrar o sistema
	 */
	public void encerrarSistema() {
		// System.exit(0);
	}

	/**
	 * Metodo para limpar a base de dados da memoria e da persistencia
	 */
	public void zerarSistema() {
		getListaDeSalas().clear();
		getListaDeEventos().clear();

	}
	
	public Iterator<Escalonavel> iteraEscalonavel(){
		indiceIteratorEsc = 0;
		return new Iterator<Escalonavel>() {
			
			public boolean hasNext() {
				return indiceIteratorEsc < getListaDeEventos().size();
			}

			public Escalonavel next() {
				Escalonavel temp = getListaDeEventos().get(indiceIteratorEsc);
				indiceIteratorEsc++;
				return temp;
			}
			
			public void remove() {
				indiceIteratorEsc--;
				Escalonavel evento = getListaDeEventos().get(indiceIteratorEsc);
				try {
					desalocarEvento(evento);
				} catch (Exception e) {
					// Vou esperar a resposta do email que mandei pro grupo de SI
					// pra saber como tratar isso aqui.
					// e ai, ele respondeu?
				}
			}
			
		};

	}
	
	public Iterator<Alocavel> iteraAlocavel(){
		indiceIteratorAloc = 0;
		return new Iterator<Alocavel>() {
			
			public boolean hasNext() {
				return indiceIteratorAloc < getListaDeSalas().size();
			}

			public Alocavel next() {
				Alocavel temp = getListaDeSalas().get(indiceIteratorAloc);
				indiceIteratorAloc++;
				return temp;
			}
			
			public void remove() {
				indiceIteratorAloc--;
				Alocavel sala = getListaDeSalas().get(indiceIteratorAloc);
				try {
					removerSala(sala);
				} catch (Exception e) {
					// Vou esperar a resposta do email que mandei pro grupo de SI
					// pra saber como tratar isso aqui.
				}
			}
			
		};

	}
	
	

}